--[[

	Gruppenmitglieder:
		Liang Shi			(0352776)
		Bogdan Ciobotaru	(337005)
		Eik Tauber			(301761)

  ]]


--[[
function var_dump(var)
	if type(var) == 'table' then
		for k,v in pairs(var) do print (k .. " => " .. tostring(v)); end
	else
		print(tostring(var));
	end
end
]]


_methodWrapperData = { superCall = false }

_GLOBAL_MT = {__index =	function(t,k)
							if k == 'super' then
								if not _methodWrapperData.currentObject then
									error('Error: super call is not allowed here!');
								end
								_methodWrapperData.superCall = true;
								return _methodWrapperData.currentObject;
							end
							if		k == 'Class'
								or	k == 'Aspect'
							then
								setmetatable(_G, {__index = function(t,k) if k=='Class' or k=='Aspect' then error('Error: '..k..' is a keyword'); end; _G[k]={_created_by='_GLOBAL_MT', _name_of_var=k}; return _G[k]; end});
								return _G['_create' .. k]; -- createClass or createAspect
							end
						end
			}


String	= 'string';
Number	= 'number';
Boolean	= 'boolean';

function _isClass (t)
	return		type(t)			== 'table'
			and	type(t._name)	== 'string'
			and	t._type			== 'Class'
			and	_G[t._name]		== t
end


--[[
	checks whether c2 is a sub class of c1
  ]]
function _isSubClassOf(c1, c2)
	-- the trivial case
	if c1 == c2 then return true; end

	-- there is no further super class
	if c2._super == nil then return false; end

	-- recursively test the same with the super class of c2
	return _isSubClassOf(c1, c2._super);
end

--[[
	Checks if the type of value is matching the declaration.
	In case value is an Object, we also check if its class is conform or equal to the declaration
  ]]
function _typeMatch(declaration,value)

	-- everything can be nil!
	if value == nil then return true; end

	if	type(value) == declaration then
		return true;
	end

	if		type(value) == 'table'
		and	_isClass(declaration)
		and	_isClass(value._class)
		and	_isSubClassOf(declaration, value._class)
	then
		return true;
	end


	return false;

end

function _methodWrapper(callingObject, callingClass, nameOfMethodToCall)
	local oldObject	= _methodWrapperData.currentObject;
	local oldClass	= _methodWrapperData.currentClass;

	if not _methodWrapperData.superCall then
		_methodWrapperData.currentObject	= callingObject;
		_methodWrapperData.currentClass		= callingClass;
	else
		if _methodWrapperData.currentClass._super == nil then
			error('Error: cannot perform super call, since superclass for Class "' .. _methodWrapperData.currentClass._name .. '" is not defined!');
		end
		_methodWrapperData.superCall		= false;
		_methodWrapperData.currentClass		= _methodWrapperData.currentClass._super;
	end

	if _methodWrapperData.currentClass._methods[nameOfMethodToCall] == nil then
		if nameOfMethodToCall == '_userDefinedCreate' then
			nameOfMethodToCall = 'create';
		end
		error('Error: unknown method "' .. nameOfMethodToCall .. '"!');
	end

	local method = _methodWrapperData.currentClass._methods[nameOfMethodToCall];

	return function(...)

		local aspectParams = {...}
		table.insert(aspectParams, 2, nameOfMethodToCall);

		----------- before methods -----------
		if nameOfMethodToCall ~= '_userDefinedCreate' then
			for _, aspect in ipairs(_getAdaptingAspectsForClass(_methodWrapperData.currentClass, false)) do
				if aspect._active then
					for methodName, pattern in pairs(aspect._beforeMethods) do
						if nameOfMethodToCall == string.match(nameOfMethodToCall, pattern) then
							local beforeMethod = aspect._methods[methodName];
							if not beforeMethod(unpack(aspectParams)) then
								return;
							end
						end
					end
				end
			end
		end
		--------------------------------------

		----------- original method -----------
		local result = method(...);
		---------------------------------------

		table.insert(aspectParams, 3, result);

		----------- after methods -----------
		if nameOfMethodToCall ~= '_userDefinedCreate' then
			for _, aspect in ipairs(_getAdaptingAspectsForClass(_methodWrapperData.currentClass, true)) do
				if aspect._active then
					for methodName, pattern in pairs(aspect._afterMethods) do
						if nameOfMethodToCall == string.match(nameOfMethodToCall, pattern) then
							local afterMethod = aspect._methods[methodName];
							aspectParams[3] = afterMethod(unpack(aspectParams));
						end
					end
				end
			end
		end
		--------------------------------------

		_methodWrapperData.currentObject	= oldObject;
		_methodWrapperData.currentClass		= oldClass;

		return aspectParams[3];
	end
end


function _createClass(params)
	if		params[1] == nil			-- do we have a first parameter? (name of the class we want to create)
		or	type(params[1]) ~= String	-- and is it a string? We could also check, if the string is conform to names of global variables in LUA.
	then
		error("Error: expected class name as first parameter");
	end

	local newClass = {};

	-- check if class is already declared. This happens if there is one or more attribute with this class as type.
	if	_G[params[1]] ~= nil then
		if		type(_G[params[1]]) == 'table'
			and	_G[params[1]]['_created_by'] == '_GLOBAL_MT'	-- _created_by is a hidden key and has to be '_GLOBAL_MT' to implicate that this value
																-- is automatically created before call and not by any user
		then
			newClass				= _G[params[1]];
			newClass._created_by	= nil;
			newClass._name_of_var	= nil;
		else
			error('Error: cannot redeclare "' .. params[1] .. '"');
		end
	else
		_G[params[1]] = newClass;
	end

	newClass._name			= params[1];
	newClass._type			= 'Class'
	newClass._attributes	= {};
	newClass._methods		= {};
	newClass._adaptedBy		= {};			-- new: stores all Aspects which adapt this class


	--------------- handling of the superClass ---------------
	if	params[2] ~= nil then		-- if set, the second parameter is the SuperClass
		if	not _isClass(params[2]) then
			error ("Error: second parameter is not a valid Class");
		end

		-- TODO: check for cycles (is this even necessary? We always check for existing classes, so it shouldn't be possible to create cycles)

		newClass._super = params[2];
		setmetatable(newClass._attributes	, {__index = newClass._super._attributes});
		setmetatable(newClass._methods		, {__index = newClass._super._methods});
		setmetatable(newClass._adaptedBy	, {__index = newClass._super._adaptedBy});	-- new: Aspects also influence all sub classes of the adapted class
	end
	----------------------------------------------------------

	for k,v in pairs(params) do
		if type(k) == 'string' then
			if		newClass._attributes[k]	~= nil
			then
				error('Error: can\'t redeclare property "' .. k .. '"!');
			end

			if		v == String
				or	v == Number
				or	v == Boolean
				or	_isClass(v)
			then
				newClass._attributes[k] = v;
			else
				if		type(v)			== 'table'
					and v._name_of_var	~= nil
				then
					error('Error: unknown type "' .. v._name_of_var .. '"!');
				else
					error('Error: unknown type for "' .. k .. '"!');
				end
			end
		end
	end

	function newClass:_defaultConstructor(...)
		local newObject =	{	_class			= self
							,	_attributes		= {}
							,	_wasDeclared	= {}
							}

		local mt = {};
		mt.__index = function(t,k)
			if		k == 'create'
			then
				return _methodWrapper(t, newClass, '_userDefinedCreate');
			end

			if				t._class._attributes[k]	== nil
				and			t._class._methods[k]	== nil
				and not	_aspectsDeclareAttribute(t._class, k)
			then
				error('Error: unknown property "' .. k ..'"!');
			end

			if		t._class._attributes[k] ~= nil
				or	_aspectsDeclareAttribute(t._class, k)
			then
				if t._attributes[k] == nil
				then
					if t._wasDeclared[k] then
						return nil;
					else
						local declaration;
						if	t._class._attributes[k] ~= nil then
							declaration = t._class._attributes[k];
						else
							declaration = _findDeclaration(t._class, k);
						end

						if declaration == String then
							return "";
						end
						if declaration == Boolean then
							return false;
						end
						if declaration == Number then
							return 0;
						end
						return nil;
					end
				else
					return t._attributes[k];
				end
			else	-- the requested property is a function
				--return t._class._methods[k];
				return _methodWrapper(t, newClass, k);
			end
		end

		mt.__newindex = function(t,k,v)
			if type(v) == 'function' then
				error('Error: cannot declare functions on an object!');
			end

			if		t._class._attributes[k] == nil
				and	not _aspectsDeclareAttribute(t._class, k)
			then
				error('Error: unknown property "' .. k ..'"!');
			end

			if		not _typeMatch(t._class._attributes[k], v)
				and	not _typeMatch(_findDeclaration(t._class, k), v)
			then
				error('Error: attachment does not match to the declaration of "' .. k .. '"!');
			end

			t._wasDeclared[k]	= true;
			t._attributes[k]	= v;

		end

		setmetatable(newObject, mt);
		if self._methods._userDefinedCreate ~= nil then
			newObject:_userDefinedCreate(...);
		end

		return newObject;
	end


	local mt = {};

	mt.__index = function(t,k)
		if k == 'create' then
			return t._defaultConstructor;
		end
	end

	mt.__newindex = function(t,k,v)
		if type(v) ~= 'function' then
			error('Error: cannot declare attributes on existing classes!');
		end

		if rawget(t._methods,k) ~= nil then
			error('Error: method "' .. k ..'" is already defined!');
		end

		if k == 'create' then
			t._methods._userDefinedCreate = v;
		else
			t._methods[k] = v;
		end
	end


	setmetatable(newClass, mt);

	setmetatable(_G, _GLOBAL_MT);

end

require ("aspekte");

setmetatable(_G, _GLOBAL_MT);
